package org.tinygroup.tinytest;

import javax.servlet.ServletContext;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.test.context.support.AbstractDelegatingSmartContextLoader;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.tinygroup.beancontainer.BeanContainerFactory;
import org.tinygroup.springutil.ExtendsSpringBeanContainer;

public class TinyWebDelegatingSmartContextLoader extends
		AbstractDelegatingSmartContextLoader {

	private final SmartContextLoader smartContextLoader;

	public TinyWebDelegatingSmartContextLoader(
			SmartContextLoader smartContextLoader) {
		super();
		this.smartContextLoader = smartContextLoader;
	}

	@Override
	public ApplicationContext loadContext(
			MergedContextConfiguration mergedConfig) throws Exception {
		try {
			AbstractApplicationContext applicationContext = (AbstractApplicationContext) smartContextLoader
					.loadContext(mergedConfig);
			ExtendsSpringBeanContainer beanContainer = (ExtendsSpringBeanContainer) BeanContainerFactory
					.getBeanContainer(getClass().getClassLoader());
			AbstractApplicationContext parentContext = (AbstractApplicationContext) beanContainer
					.getBeanContainerPrototype();
			addBeanFactoryPostProcessors(applicationContext, parentContext);
			addBeanPostProcessors(applicationContext, parentContext);
			return applicationContext;

		} catch (IllegalStateException e) {
			ExtendsSpringBeanContainer beanContainer = (ExtendsSpringBeanContainer) BeanContainerFactory
					.getBeanContainer(getClass().getClassLoader());
			return beanContainer.getBeanContainerPrototype();
		}
	}

	protected void configureWebResources(GenericWebApplicationContext context,
			WebMergedContextConfiguration webMergedConfig) {

		ApplicationContext parent = context.getParent();

		// if the WAC has no parent or the parent is not a WAC, set the WAC as
		// the Root WAC:
		if (parent == null || (!(parent instanceof WebApplicationContext))) {
			String resourceBasePath = webMergedConfig.getResourceBasePath();
			ResourceLoader resourceLoader = resourceBasePath
					.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
					: new FileSystemResourceLoader();

			ServletContext servletContext = new MockServletContext(
					resourceBasePath, resourceLoader);
			servletContext
					.setAttribute(
							WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
							context);
			context.setServletContext(servletContext);
		} else {
			ServletContext servletContext = null;
			// find the Root WAC
			while (parent != null) {
				if (parent instanceof WebApplicationContext
						&& !(parent.getParent() instanceof WebApplicationContext)) {
					servletContext = ((WebApplicationContext) parent)
							.getServletContext();
					break;
				}
				parent = parent.getParent();
			}
			Assert.state(servletContext != null,
					"Failed to find Root WebApplicationContext in the context hierarchy");
			context.setServletContext(servletContext);
		}
	}

	@Override
	protected SmartContextLoader getXmlLoader() {
		return null;
	}

	@Override
	protected SmartContextLoader getAnnotationConfigLoader() {
		return null;
	}
	
	private void addBeanPostProcessors(
			AbstractApplicationContext applicationContext,
			ApplicationContext parentContext) {
		DefaultListableBeanFactory parentListableBeanFactory = (DefaultListableBeanFactory) parentContext
				.getAutowireCapableBeanFactory();
		DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext
				.getAutowireCapableBeanFactory();
		for (BeanPostProcessor beanPostProcessor : parentListableBeanFactory
				.getBeanPostProcessors()) {
			defaultListableBeanFactory.addBeanPostProcessor(beanPostProcessor);
		}
	}

	private void addBeanFactoryPostProcessors(
			AbstractApplicationContext applicationContext,
			AbstractApplicationContext parentContext) {
		for (BeanFactoryPostProcessor beanFactoryPostProcessor : parentContext
				.getBeanFactoryPostProcessors()) {
			applicationContext
					.addBeanFactoryPostProcessor(beanFactoryPostProcessor);
		}
		String[] postProcessorNames = applicationContext.getBeanNamesForType(
				BeanFactoryPostProcessor.class, true, false);
		for (String ppName : postProcessorNames) {
			applicationContext.addBeanFactoryPostProcessor(parentContext
					.getBean(ppName, BeanFactoryPostProcessor.class));
		}
	}


}
